#!/bin/python

#Author: Chao Wang
#Email:  chw412@lehigh.edu

###################################################################
#
#This program calculate the edit distance of two sequences, which can directly come from user's input or from the
#two files containing the two sequences.
#
#This program also draw the edit distance matrix, and back track matrix and the alignment of the two sequeences
#as well
#
#Note this program is adapted from the program of mad_lib.py from Professor Lopresti
#
#

from Tkinter import *


class Application(Frame):
    """ GUI application that creates a story based on user input. """
    def __init__(self, master):
        Frame.__init__(self, master)
        self.grid()
        self.create_widgets()

	#when users click different RadioButtons, selecting sequences input methods, the relative GUI should
	#update
    def update_label(self):
	    if self.string_or_file.get() == "file names":
		    self.label_a.config(text = "A's file name:")
		    self.label_b.config(text = "B's file name:")
	    elif self.string_or_file.get() == "strings":
		    self.label_a.config(text = "Sequence A:")
		    self.label_b.config(text = "Sequence B:")
    def create_widgets(self):
        """ Create widgets to get story information and to display story. """
        # create instruction label
        Label(self,
              text = "Enter two "
              ).grid(row = 0, column = 0, rowspan = 2)


	#Radio Buttons asking users to select one way of sequence input method
	self.string_or_file = StringVar()
	self.rb1 = Radiobutton(self,
	      		text = "strings",
			variable = self.string_or_file,
			value = "strings",
			command = self.update_label
	      		)
	self.rb1.grid(row = 0, column = 1, sticky = W)

	self.rb2 = Radiobutton(self,
	      		text = "file names",
			variable = self.string_or_file,
			value = "file names",
			command = self.update_label
	      		)
        self.rb2.grid(row = 1, column = 1, sticky = W)

	#Labels and entries asking users to input the two sequences or two files names
        self.label_a = Label(self,
              		text = "Sequence A:"
              		)
	self.label_a.grid(row = 2, column = 0, sticky = W)
        self.stringA_ent = Entry(self, width = 12)
        self.stringA_ent.grid(row = 3, column = 0, sticky = W)

        self.label_b = Label(self,
              text = "Sequence B:"
              )
	self.label_b.grid(row = 2, column = 1, sticky = W)
        self.stringB_ent = Entry(self, width = 12)
        self.stringB_ent.grid(row = 3, column = 1, sticky = W)

	#three Scales used to let the users select the three parameters: cost of deletion, cost of insertion and
	#cost of substition
        Label(self,
	      text = "cdel:"
              ).grid(row = 4, column = 0, sticky = W+S+E+N)
	self.cdel_ent = Scale(self, from_=1, to=10, orient=HORIZONTAL)
	self.cdel_ent.pack()
        self.cdel_ent.grid(row = 4, column = 1, sticky = W)
        
	Label(self,
              text = "cins:"
              ).grid(row = 5, column = 0, sticky = W+S+E+N)
        self.cins_ent = Scale(self, from_=1, to=10, orient=HORIZONTAL)
	self.cins_ent.pack()
        self.cins_ent.grid(row = 5, column = 1, sticky = W)
        
	Label(self,
              text = "csub:"
              ).grid(row = 6, column = 0, sticky = W+S+E+N)
        self.csub_ent = Scale(self, from_=1, to=10, orient=HORIZONTAL)
	self.csub_ent.pack()
        self.csub_ent.grid(row = 6, column = 1, sticky = W)
  
        # create a label for users to select preferences
        Label(self,
              text = "Output preference:"
              ).grid(row = 7, column = 0, columnspan = 2, sticky = W)

        # create edit distance matrix check button
        self.is_edit_distance_matrix = BooleanVar()
        Checkbutton(self,
                    text = "full edit distance matrix",
                    variable = self.is_edit_distance_matrix
                    ).grid(row = 8, column = 0, columnspan = 2, sticky = W)

        # create back track matrix check button
        self.is_back_track_matrix = BooleanVar()
        Checkbutton(self,
                    text = "back tract matrix",
                    variable = self.is_back_track_matrix
                    ).grid(row = 9, column = 0, columnspan = 2, sticky = W)

        # create alignment check button
        self.is_alignment = BooleanVar()
        Checkbutton(self,
                    text = "alignment",
                    variable = self.is_alignment
                    ).grid(row = 10, column = 0, columnspan = 2, sticky = W)

        # create a submit button. When users click, the comparing result is produced.
        Button(self,
               text = "Compare",
               command = self.compare
               ).grid(row = 11, column = 0, rowspan = 2, columnspan = 2, sticky = N+S)


	#horizental and vertical scrollbars associated with the Canvas, where we we produce the output result.
	self.hbar = Scrollbar(self, orient=HORIZONTAL)
	self.vbar = Scrollbar(self, orient=VERTICAL)	
	self.hbar.grid(row = 15, column = 2, sticky = W+S+N+E)
	self.vbar.grid(row = 0, column = 3, rowspan = 15, sticky = W+N+S+E)
	self.canvas = Canvas(self, bg = "white", width=1100, height=650, \
				xscrollcommand = self.hbar.set, yscrollcommand = self.vbar.set,
				scrollregion = (0,0, 1100, 650))
	self.canvas.grid(row = 0, column = 2, rowspan = 15, sticky = W+N+S+E)

	self.hbar.config(command = self.canvas.xview)
	self.vbar.config(command = self.canvas.yview)

	#several parameters where we use for drawing nice-looking pictures
	self.font = ('courier', 16)
	self.char_space = 32
	self.matrix_space = 100

	#a variable recording the start of pixels of this run
	self.pixel_lines = 0 
    def compare(self):
        # get two sequences or file names from the GUI 
        self.stringA = self.stringA_ent.get()
        self.stringB = self.stringB_ent.get()
       
       	#if we've got two file names, just read the content as two strings
	if self.string_or_file.get() == "file names":
		input1 = open(self.stringA, "r")
		self.stringA = ""
		try:
			self.stringA = input1.read()
		finally:
			input1.close()

		self.stringA = self.stringA[0:len(self.stringA) - 1]
		input2 = open(self.stringB, "r")
		try:
			self.stringB = input2.read()
		finally:
			input2.close()
		self.stringB = self.stringB[0:len(self.stringB) - 1]
		print self.stringA
		print self.stringB
	#if we've got two strings, use them as the two sequences
	elif self.string_or_file.get() == "strings":
		pass
	else:
		assert(0)

	#get the values of three parameters from the GUI
	self.cdel = self.cdel_ent.get()
	self.cins = self.cins_ent.get()
	self.csub = self.csub_ent.get()

	#the main function that calcluate all the two matrixes and the alignment
        self.calculate_edit_distance()
       
        print "edit distance = %d" % (self.dict[len(self.stringA), len(self.stringB)])
	print "%d %d %d" % (self.is_edit_distance_matrix.get(), self.is_back_track_matrix.get(), self.is_alignment.get())

	#generate the traces of comparison
	self.generate_traces()
	
#	self.canvas.delete(ALL)


	#generate the three items as user requsets
	if self.is_edit_distance_matrix.get():
		self.print_edit_distance_matrix()
	if self.is_back_track_matrix.get():
		self.print_back_track_matrix()
	if self.is_alignment.get():
		self.print_alignment()

	#place bounderaies for each set of output.
	if self.is_edit_distance_matrix.get() or self.is_back_track_matrix.get() or self.is_alignment.get():
		self.canvas.create_line(0, \
				self.pixel_lines, \
				self.matrix_space + (len(self.stringB) * self.char_space + self.matrix_space) * 2 + len(self.trace) * self.char_space,\
				self.pixel_lines, fill = 'green')
		self.canvas.create_line(0, \
				self.pixel_lines + 5, \
				self.matrix_space + (len(self.stringB) * self.char_space + self.matrix_space) * 2 + len(self.trace) * self.char_space,\
				self.pixel_lines + 5, fill = 'green')
		self.canvas.create_text((self.matrix_space + (len(self.stringB) * self.char_space +
			self.matrix_space) * 2 + len(self.trace) * self.char_space) / 2, \
				self.pixel_lines + self.matrix_space + len(self.stringA) * self.char_space +
				self.matrix_space - 10, \
					text = "Edit Distance = " +
					str(self.dict[(len(self.stringA),len(self.stringB))]) + "  cdel:" +
					str(self.cdel) + "  cins:" + str(self.cins) + "  csub:" + str(self.csub), \
					fill = 'red', font = self.font)
		self.canvas.create_line(0, \
				self.pixel_lines + self.matrix_space + len(self.stringA) * self.char_space + self.matrix_space, \
				self.matrix_space + (len(self.stringB) * self.char_space + self.matrix_space) * 2 + len(self.trace) * self.char_space,\
				self.pixel_lines + self.matrix_space + len(self.stringA) * self.char_space + self.matrix_space, fill = 'green')
		self.canvas.create_line(0, \
				self.pixel_lines + self.matrix_space + len(self.stringA) * self.char_space + self.matrix_space + 5, \
				self.matrix_space + (len(self.stringB) * self.char_space + self.matrix_space) * 2 + len(self.trace) * self.char_space,\
				self.pixel_lines + self.matrix_space + len(self.stringA) * self.char_space + self.matrix_space + 5, fill = 'green')
	else: 
		self.canvas.create_text((self.matrix_space + (len(self.stringB) * self.char_space +
			self.matrix_space) * 2 + len(self.trace) * self.char_space) / 2, \
				self.pixel_lines + self.matrix_space  +
				self.matrix_space - 10, \
					text = "Edit Distance = " +
					str(self.dict[(len(self.stringA),len(self.stringB))]) + "  cdel:" +
					str(self.cdel) + "  cins:" + str(self.cins) + "  csub:" + str(self.csub), \
					fill = 'red', font = self.font)

	if self.is_edit_distance_matrix.get() or self.is_back_track_matrix.get() or self.is_alignment.get():
		self.pixel_lines += self.matrix_space + len(self.stringA) * self.char_space + self.matrix_space + 30
		self.canvas.config(scrollregion = (0, 0, \
			self.matrix_space + ((len(self.stringB) + 1)* self.char_space + self.matrix_space) * 2 +
			(len(self.trace) + 1) * self.char_space, \
			self.pixel_lines + self.matrix_space + len(self.stringA) * self.char_space + self.matrix_space + 5))
	else:
		self.pixel_lines += 2*self.matrix_space + 30
		self.canvas.config(scrollregion = (0, 0, \
			3*self.matrix_space, \
			self.pixel_lines + self.matrix_space + self.matrix_space + 5))

	#this is the function where the two matrixes are calculated
    def calculate_edit_distance(self):
	self.dict = {}
	self.track = {}
	self.dict[(0,0)] = 0
	self.track[(0,0)] = '0'

	for i in range(1, len(self.stringA) + 1):
		self.dict[(i,0)] = i*self.cdel
	for j in range(1, len(self.stringB) + 1):
		self.dict[(0,j)] = j*self.cins
	for i in range(1, len(self.stringA) + 1):
		self.track[(i,0)] = '|'
	for j in range(1, len(self.stringB) + 1):
		self.track[(0,j)] = '-'

	
	for i in range(1, len(self.stringA) + 1):
		for j in range(1, len(self.stringB) + 1):
			min = (self.cdel + self.cins + self.csub)*len(self.stringA)*len(self.stringB);
			
			if self.dict[(i-1, j)] + self.cdel < min:
				min = self.dict[(i-1, j)] + self.cdel
				self.track[(i,j)] = '|'

			if self.dict[(i, j-1)] + self.cins < min:
				min = self.dict[(i, j-1)] + self.cins
				self.track[(i,j)] = '-'
			
			if self.stringA[i-1] != self.stringB[j-1]:
				match_mismatch_cost = self.csub
			else:
				match_mismatch_cost = 0
			
			if self.dict[(i-1, j-1)] + match_mismatch_cost < min:
				min = self.dict[(i-1, j-1)] + match_mismatch_cost
				self.track[(i,j)] = '\\'
			
			self.dict[(i,j)] = min
	
	#print the edit distance matrix of the two sequences
    def print_edit_distance_matrix(self):

	for i in range(1, len(self.stringA) + 1):
		self.canvas.create_text(self.matrix_space - self.char_space, self.pixel_lines + self.matrix_space + i*self.char_space, text = self.stringA[i - 1], \
				fill = 'black', font = self.font)
	
	for j in range(1, len(self.stringB) + 1):
		self.canvas.create_text(self.matrix_space + j*self.char_space, self.pixel_lines + self.matrix_space - self.char_space, text = self.stringB[j - 1], \
				fill = 'black', font = self.font)

	for i in range(0, len(self.stringA) + 1):
		for j in range(0, len(self.stringB) + 1):
			print "%d" % (self.dict[(i,j)]),
			self.canvas.create_text(self.matrix_space + j*self.char_space, self.pixel_lines + self.matrix_space + i*self.char_space, \
					text = str(self.dict[(i,j)]), \
					fill = 'red', font = self.font)
		print "\n"
	print "\n"

	self.pack()

	#print the back track matrix on the canvas
    def print_back_track_matrix(self):
	for i in range(1, len(self.stringA) + 1):
		self.canvas.create_text(self.matrix_space + len(self.stringB) * self.char_space +
				self.matrix_space - self.char_space, self.pixel_lines + self.matrix_space + i*self.char_space, \
				text = self.stringA[i - 1], fill = 'black', font = self.font)
	
	for j in range(1, len(self.stringB) + 1):
		self.canvas.create_text(self.matrix_space + len(self.stringB) * self.char_space +
				self.matrix_space + j*self.char_space, self.pixel_lines + self.matrix_space - self.char_space, \
				text = self.stringB[j - 1], fill = 'black', font = self.font)

	for i in range(0, len(self.stringA) + 1):
		for j in range(0, len(self.stringB) + 1):
			print "%c" % (self.track[(i,j)]),
			self.canvas.create_text(self.matrix_space + len(self.stringB) * self.char_space + self.matrix_space + j*self.char_space, \
						self.pixel_lines + self.matrix_space + i*self.char_space, \
						text = self.track[(i,j)], \
						fill = 'brown', font = self.font)
		print "\n"
	print "\n"
	
	#generate the traces from back to the front
    def generate_traces(self):
	self.trace = []
	i = len(self.stringA)
	j = len(self.stringB)
	while i >= 1 or j >= 1:
		self.trace.insert(0, self.track[(i,j)])
		if self.track[(i,j)] == '|':
			i -= 1
		elif self.track[(i,j)] == '-':
			j -= 1
		elif self.track[(i,j)] == '\\':
			i -= 1
			j -= 1
    
	#print alignment 
    def print_alignment(self):
	pos = 0
	i = 0
    	for ch in self.trace:
		if ch == '-':
			print "- ",
			self.canvas.create_text(self.matrix_space + (len(self.stringB) * self.char_space +
				self.matrix_space) * 2 + pos*self.char_space, self.pixel_lines + self.matrix_space, text = '-', fill = 'blue', font = self.font)
		elif ch == '|' or ch == '\\':
			print "%c " % (self.stringA[i]),
			self.canvas.create_text(self.matrix_space + (len(self.stringB) * self.char_space +
				self.matrix_space) * 2 + pos*self.char_space, self.pixel_lines + self.matrix_space, text = self.stringA[i], fill = 'blue', font = self.font)
			i += 1
		pos += 1

	print "\n"
	
	pos = 0
	i = 0
	j = 0
	for ch in self.trace:
		if ch == '\\':
			print "| ",
			if self.stringA[i] == self.stringB[j]:
				self.canvas.create_line(self.matrix_space + (len(self.stringB) * self.char_space
					+ self.matrix_space) * 2 + pos*self.char_space, self.pixel_lines + self.matrix_space + 10,
					self.matrix_space + (len(self.stringB) * self.char_space +
						self.matrix_space) * 2 + pos*self.char_space, self.pixel_lines + self.matrix_space +
					len(self.stringA) * self.char_space - 10, fill = 'green')
			else:
				self.canvas.create_line(self.matrix_space + (len(self.stringB) * self.char_space
					+ self.matrix_space) * 2 + pos*self.char_space, self.pixel_lines + self.matrix_space + 10,
					self.matrix_space + (len(self.stringB) * self.char_space +
						self.matrix_space) * 2 + pos*self.char_space, self.pixel_lines + self.matrix_space +
					len(self.stringA) * self.char_space - 10, fill = 'red')
			i += 1
			j += 1
		else:
			print "  ",
			if ch == '-':
				j += 1
			else:
				i += 1
		pos += 1
	print "\n"
	
	pos = 0
	j = 0
	for ch in self.trace:
		if ch == '|':
			print "- ",
			self.canvas.create_text(self.matrix_space + (len(self.stringB) * self.char_space +
				self.matrix_space) * 2 + pos*self.char_space, self.pixel_lines + self.matrix_space + len(self.stringA) * self.char_space, text = '-', fill = 'blue', font = self.font)
		elif ch == '-' or ch == '\\':
			print "%c " % (self.stringB[j]),
			self.canvas.create_text(self.matrix_space + (len(self.stringB) * self.char_space +
				self.matrix_space) * 2 + pos*self.char_space, self.pixel_lines + self.matrix_space + len(self.stringA) * self.char_space, text = self.stringB[j], fill = 'blue', font = self.font)
			j += 1
		pos += 1
	print "\n"
# main
root = Tk()
root.title("Compare two sequences")
root.geometry("1400x742")
app = Application(root)
root.mainloop()

